#include "stdafx.h"
#include "Environment.h"
#include "Lexecutioner.h"
#include "Support.h"
#include "MemoryArea.h"
#include "Variable.h"
#include "../Parser/Vals.h"
#include "Errors.h"
#include <cstdlib>
#include <boost/foreach.hpp>
#include <functional>
#include "MiniBasic_h.h"
#include <boost/lexical_cast.hpp>

namespace MiniBasic
{
    // declaration so we don't need to include parser/parser.hpp
    extern int yyparse(void*);

    Environment::Environment(ExecutionParameters* pExParams) : userMemory(pExParams->memoryAreaSize), printNumberPadding(6), conManager(!!(pExParams->keepConsoleOpen))
    {
        singleLetterVariables.reserve(26);
        for(char ch = 'A'; ch <= 'Z'; ++ch)
        {
            singleLetterVariables.push_back(Variable(std::string(1, ch), 0));
        }
        srand(GetTickCount());
    }

    Variable* Environment::GetArrayVar(mbint location)
    {
        if(location >= 0)
        {
            std::map<mbint, Variable>::iterator iter = arrayVariables.find(location);
            if(iter == arrayVariables.end())
            {
                std::ostringstream str;
                str << "@(" << location << ')';
                std::pair<std::map<mbint, Variable>::iterator, bool> newEntry = arrayVariables.insert(std::make_pair(location, Variable(str.str(), 0)));
                return &(newEntry.first->second);
            }
            else
            {
                return &(iter->second);
            }
        }
        else
        {
            return 0;
        }
    }

    Variable* Environment::GetLetterVar(char letter)
    {
        return &singleLetterVariables[toupper(letter) - 'A'];
    }

    bool Environment::EnterForLoop(Lexecutioner* lex, const var_type& loopVariable, const var_type& exitCondition, mbint step)
    {
        using namespace Errors;
        ErrorSink error(this);
        bool success = false;
        Variable* loopVar;
        if(GetExprValue(loopVariable, loopVar))
        {
            mbint currentLine = lex->GetCurrentLine()->first;
            std::vector<ForLoop>::iterator iter;
            BOOST_FOREACH(ForLoop& loop, forStack)
            {
                if(loopVar == loop.var)
                {
                    // for loops with the same variable replace the earlier incarnation
                    // and are not an error condition
                    loop.beenReplaced = true;
                }
            }
            mbint loopExit;
            if(GetExprValue(exitCondition, loopExit))
            {
                // if the current file pointer is at a newline char
                // move past it so the for body starts at the
                // beginning of the next line
                ULONG_PTR forStart = lex->GetCurrentFilePosition(true);
                if(forStart != MAXULONG_PTR)
                {
                    forStack.push_back(ForLoop(step, currentLine, forStart, loopExit, loopVar));
                    success = true;
                }
                else
                {
                    error << "Line " << currentLine << ": " << INTERNAL_FUNCTION_FAILURE << " retrieving current file position";
                }
            }
            else
            {
                error << "Line " << currentLine << ": " << EXPECTED_INTEGER << " as FOR exit condition";
            }
        }
        return success;
    }

    int Environment::EvaluateTopFor(Lexecutioner* lex, Variable* var) const
    {
        using namespace Errors;
        ErrorSink error(this);
        int retVal = 0;
        if(!forStack.empty())
        {
            const ForLoop& loop = forStack.back();
            if(var)
            {
                if(var != loop.var)
                {
                    error << "Line " << lex->GetCurrentLine()->first << ": " << NOT_FOR_VARIABLE;
                    return retVal;
                }
            }
            // if the loop has been replaced by another use of the loop variable
            // then just ignore the loop condition, and carry on regardless
            if(!loop.beenReplaced)
            {
                mbint& variableValue = boost::get<mbint>(loop.var->value);
                variableValue += loop.increment;
                if(variableValue <= loop.exitCondition)
                {
                    lex->MoveToFilePosition(loop.loopBodyStart);
                    retVal = CONTINUE_EXECUTION;
                }
                else
                {
                    retVal = FOR_EXITED;
                }
            }
            else
            {
                retVal = FOR_EXITED;
            }
        }
        else
        {
            error << "Line " << lex->GetCurrentLine()->first << ": " << INVALID_NEXT;
        }
        return retVal;
    }

    void Environment::ExitFor()
    {
        if(!forStack.empty())
        {
            forStack.pop_back();
        }
    }

    bool Environment::EnterSub(Lexecutioner* lex, mbint line)
    {
        bool lineExists = lex->LineExists(line);
        if(lineExists)
        {
            ULONG_PTR currentPos = lex->GetCurrentFilePosition(true);
            gosubStack.push(currentPos);
            lex->MoveToLine(line);
        }
        else
        {
            Errors::ErrorSink error(this);
            error << "Line " << lex->GetCurrentLine()->first << ": " << Errors::NOT_A_LINE << "(" << line << ") in GOSUB statement";
        }
        return lineExists;
    }

    bool Environment::ExitSub(Lexecutioner* lex)
    {
        if(!gosubStack.empty())
        {
            ULONG_PTR newPosition = gosubStack.top();
            gosubStack.pop();
            lex->MoveToFilePosition(newPosition);
            return true;
        }
        else
        {
            return false;
        }
    }

    HRESULT Environment::DoExecution(Lexecutioner& lexer)
    {
        // the execution path is as follows
        // slightly round the houses due to the idiosyncrasies of flex and bison
        // and their implementations of reentrant parsing
        //
        // parseparams created here including a self pointer
        // and a pointer to Lexecutioner, which does the lexing
        // 
        // yyparse(yylval, parseParams) -> Free function defined in Parser.h
        // yylex(yylval, parserParams) -> same as above, uses the lexer pointer to call
        // Lexecutioner->yylex(yylval, parseParams) -> builtin functions use the env pointer
        // keep
        try
        {
            ParseParams parseParams = {this, &lexer};
            int ret = 0;
            while((ret = MiniBasic::yyparse(&parseParams)) == CONTINUE_EXECUTION);
            return S_OK;
        }
        catch(HRESULT hr)
        {
            return hr;
        }
    }

    bool Environment::DoInput(const var_type& v1, mbint currentLine)
    {
        using namespace Errors;
        bool success = true;
        std::string toDisplay;
        bool getInput = false;
        Variable* vt;
        if(!GetExprValue(v1, vt)) // not a variable
        {
            toDisplay = StringifyExpression(v1);
            toDisplay.push_back(':');
        }
        else
        {
            getInput = true;
        }
        conManager.OutputText(toDisplay);
        if(getInput)
        {
            std::string input;
            if(conManager.GetFromUserWithEnter(input))
            {
                mbint integer = 0;
                try
                {
                    integer = boost::lexical_cast<mbint>(input);
                    vt->value = integer;
                }
                catch(const boost::bad_lexical_cast&)
                {
                    // not an int, so if it's not empty, try and parse it as an expression
                    if(!input.empty())
                    {
                        // prepend a line number and append a newline to keep the lexer happy
                        input.insert(0, "1 ");
                        input.push_back('\r');
                        try
                        {
                            Lexecutioner exprLexer(input.c_str(), input.length(), true);
                            DoExecution(exprLexer);
                        }
                        catch(HRESULT hr)
                        {
                            ErrorSink error(this);
                            error << INTERNAL_FUNCTION_FAILURE << " while parsing user input on line ";
                            error << currentLine << '\n';
                            error << "Details: Couldn't tokenize user input due to HRESULT error " << std::hex << hr;
                            success = false;
                        }
                    }
                }
            }
            else
            {
                ErrorSink error(this);
                error << "Expected numeric input";
                success = false;
            }
        }
        return success;
    }

    void Environment::DoOutput(const std::string& outString) const
    {
        bool hasTrailingComma = (!outString.empty() && (*(outString.end() - 1) == ','));
        std::string stringToOutput = outString;
        if(hasTrailingComma)
        {
            stringToOutput.erase(stringToOutput.end() - 1);
        }
        conManager.OutputText(stringToOutput);
        if(!hasTrailingComma)
        {
            conManager.OutputText("\n");
        }
    }

    void Environment::SetPadding(mbint spaces)
    {
        printNumberPadding = PADDING_CHANGED_TAG | spaces;
    }
    
    mbint Environment::GetPadding() const 
    {
        return printNumberPadding;
    }

    void Environment::ResetPadding()
    {
        printNumberPadding = 6;
    }
}